package org.pgis.core;

import ij.ImagePlus;
import ij.WindowManager;

import java.awt.Color;
import java.io.IOException;

import javax.swing.JOptionPane;

import org.pgis.entity.ComponentViewEnum;
import org.pgis.entity.ImageProperty;
import org.pgis.entity.RepresentationEnum;

import net.sf.ij_plugins.vtk.utils.VTKImageDataFactory;

import vtk.vtkActor;
import vtk.vtkClipPolyData;
import vtk.vtkContourFilter;
import vtk.vtkDecimatePro;
import vtk.vtkImageData;
import vtk.vtkOutlineFilter;
import vtk.vtkPlane;
import vtk.vtkPolyDataMapper;
import vtk.vtkPolyDataNormals;
import vtk.vtkSmoothPolyDataFilter;
import vtk.vtkStripper;
import vtk.vtkTriangleFilter;

public abstract class AbstractImageModel implements ImageModel {

	protected vtkActor actor;
	protected vtkActor outlineActor;
	protected vtkPolyDataNormals actorNormals;
	protected vtkSmoothPolyDataFilter actorSmooth;
	protected vtkDecimatePro actorDecimate;
	protected ImagePlus imagePlus;
	protected int imageWidth;
	protected int imageHeight;
	protected int stackSize;
	//protected int clipXplus, clipXminus, clipYplus, clipYminus, clipZplus, clipZminus;
	protected vtkPlane planeXplus, planeXminus, planeYplus, planeYminus, planeZplus, planeZminus;
	protected ImageProperty imageProperty = new ImageProperty();
	public static final int FACTOR_10 = 10;
	public static final int FACTOR_100 = 100;
	public static final int FACTOR_255 = 255;


	static {
        System.loadLibrary("vtkCommonJava");
        System.loadLibrary("vtkFilteringJava");
        System.loadLibrary("vtkIOJava");
        System.loadLibrary("vtkImagingJava");
        System.loadLibrary("vtkGraphicsJava");
        System.loadLibrary("vtkRenderingJava");
    }

	public AbstractImageModel() {
		  actor = new vtkActor();

	      actorSmooth = new vtkSmoothPolyDataFilter();
	      vtkImageData imageData = null;
	      try {
	    	  imageData = createImageFromCurrentStack();
	      } catch (RuntimeException e) {
	    	  return;
	      }
	      actorSmooth = createSmoothingProperties(imageData);
	      actorNormals = createNormalProperties(actorSmooth);
	      actorDecimate = createDecimate(actorNormals);

	      outlineActor = createOutline(imageData);

	      vtkPolyDataMapper mapper = setCliping(actorDecimate);
	      actor.SetMapper(mapper);

	      setDefaultActorProperties();

	}

	private vtkImageData createImageFromCurrentStack() {

		if(WindowManager.getCurrentImage() != null) {
	    	imagePlus = WindowManager.getCurrentImage();
	    	vtkImageData imageData;
	    	try {
	    		imageData = VTKImageDataFactory.create(imagePlus);
			} catch (IOException e) {
				e.printStackTrace();
				throw new RuntimeException("No image stack found");
			}

	    	if (imagePlus == null) {
                JOptionPane.showMessageDialog(null, "Stack is not available");
                throw new RuntimeException("Image Plus couldn't find");
            }else if (imagePlus.getType() != ImagePlus.GRAY8) {
                JOptionPane.showMessageDialog(null, "8 bit conversion is needed");
                throw new RuntimeException("Only Gray 8! image type is allowed");
            }

            imageWidth = imagePlus.getWidth();
            imageHeight = imagePlus.getHeight();
            stackSize = imagePlus.getStackSize();
            imageProperty.setClipXminus(imageWidth);
            imageProperty.setClipYminus(imageHeight);
            imageProperty.setClipZminus(stackSize);

            return imageData;
		} else {
			throw new RuntimeException("No image stack found");
		}
	}

	private vtkSmoothPolyDataFilter createSmoothingProperties(vtkImageData imageData) {
		vtkContourFilter skinExtractors = new vtkContourFilter();
		skinExtractors.SetInput(imageData);
		skinExtractors.SetValue(0, 128);
		vtkSmoothPolyDataFilter smooth = new vtkSmoothPolyDataFilter();
		smooth.SetInput(skinExtractors.GetOutput());
		smooth.SetNumberOfIterations(imageProperty.getNumberOfIterations());
		smooth.SetRelaxationFactor(((float)imageProperty.getRelaxationFactor() / FACTOR_100));

		return smooth;
	}

	private vtkPolyDataNormals createNormalProperties(vtkSmoothPolyDataFilter smooth) {
		vtkPolyDataNormals normals = new vtkPolyDataNormals();
		normals.SetInput(smooth.GetOutput());
		normals.SetFeatureAngle(60.0);
		normals.FlipNormalsOn();

		return normals;
	}

	private vtkDecimatePro createDecimate(vtkPolyDataNormals noramls){
		vtkStripper skinStripper = new vtkStripper();
        skinStripper.SetInput(noramls.GetOutput());

        vtkTriangleFilter triangles = new vtkTriangleFilter();
        triangles.SetInput(skinStripper.GetOutput());

        vtkDecimatePro decimate = new vtkDecimatePro();
        decimate.SetInput(triangles.GetOutput());
        decimate.SetTargetReduction(((float)imageProperty.getTriangleQuality() / FACTOR_100));
        decimate.PreserveTopologyOn();
        decimate.SetFeatureAngle(60);
        decimate.SplittingOn();
        decimate.AccumulateErrorOn();
        decimate.SetMaximumError(0.3);

        return decimate;
	}

	private void definePlanes() {
		planeZplus = new vtkPlane();
		planeZplus.SetNormal(0, 0, 1);
		planeZplus.SetOrigin(0, 0, imageProperty.getClipZplus());

		planeZminus = new vtkPlane();
		planeZminus.SetNormal(0, 0, -1);
		planeZminus.SetOrigin(0, 0, imageProperty.getClipZminus());

		planeXplus = new vtkPlane();
		planeXplus.SetNormal(1, 0, 0);
		planeXplus.SetOrigin(imageProperty.getClipXplus(), 0, 0);

		planeXminus = new vtkPlane();
		planeXminus.SetNormal(-1, 0, 0);
		planeXminus.SetOrigin(imageProperty.getClipXminus(), 0, 0);

        planeYplus = new vtkPlane();
        planeYplus.SetNormal(0, 1, 0);
        planeYplus.SetOrigin(0, imageProperty.getClipYplus(), 0);

        planeYminus = new vtkPlane();
        planeYminus.SetNormal(0, -1, 0);
        planeYminus.SetOrigin(0, imageProperty.getClipYminus(), 0);
	}

	private void setDefaultActorProperties() {
		actor.GetProperty().SetRepresentationToWireframe();
		actor.GetProperty().SetColor((float)(imageProperty.getObjectColor().getRed() / FACTOR_255),
				(float)(imageProperty.getObjectColor().getBlue() / FACTOR_255),
				(float)(imageProperty.getObjectColor().getGreen() / FACTOR_255));
		actor.GetProperty().SetDiffuseColor((float)(imageProperty.getDiffuseColor()/FACTOR_10),
				(float)(imageProperty.getDiffuseColor()/FACTOR_10),
				(float)(imageProperty.getDiffuseColor()/FACTOR_10));
		actor.GetProperty().SetSpecular((float)(imageProperty.getSpecularColor() / FACTOR_10));
		actor.GetProperty().SetSpecularPower(10);
		actor.GetProperty().SetAmbient((float)(imageProperty.getAmbientColor() / FACTOR_10));
		actor.GetProperty().SetOpacity((float)(imageProperty.getOpacity() / FACTOR_10));

		actor.GetProperty().FrontfaceCullingOff();
		actor.GetProperty().BackfaceCullingOff();
		actor.PickableOn();
		actor.SetPickable(1);
		actor.SetDragable(1);
	}

	private vtkPolyDataMapper setCliping(vtkDecimatePro decimate) {

		definePlanes();

        vtkClipPolyData clipXplusPolyData = new vtkClipPolyData();
        clipXplusPolyData.SetInput(decimate.GetOutput());
        clipXplusPolyData.SetClipFunction(planeXplus);
        clipXplusPolyData.GenerateClippedOutputOn();

        vtkClipPolyData clipXminusPolyData = new vtkClipPolyData();
        clipXminusPolyData.SetInput(clipXplusPolyData.GetOutput());
        clipXminusPolyData.SetClipFunction(planeXminus);
        clipXminusPolyData.GenerateClippedOutputOn();

        vtkClipPolyData clipYplusPolyData = new vtkClipPolyData();
        clipYplusPolyData.SetInput(clipXminusPolyData.GetOutput());
        clipYplusPolyData.SetClipFunction(planeYplus);
        clipYplusPolyData.GenerateClippedOutputOn();

        vtkClipPolyData clipYminusPolyData = new vtkClipPolyData();
        clipYminusPolyData.SetInput(clipYplusPolyData.GetOutput());
        clipYminusPolyData.SetClipFunction(planeYminus);
        clipYminusPolyData.GenerateClippedOutputOn();

        vtkClipPolyData clipZplusPolyData = new vtkClipPolyData();
        clipZplusPolyData.SetInput(clipYminusPolyData.GetOutput());
        clipZplusPolyData.SetClipFunction(planeZplus);
        clipZplusPolyData.GenerateClippedOutputOn();

        vtkClipPolyData clipZminusPolyData = new vtkClipPolyData();
        clipZminusPolyData = new vtkClipPolyData();
        clipZminusPolyData.SetInput(clipZplusPolyData.GetOutput());
        clipZminusPolyData.SetClipFunction(planeZminus);
        clipZminusPolyData.GenerateClippedOutputOn();

        vtkPolyDataMapper polyDataMapper = new vtkPolyDataMapper();
        polyDataMapper.SetScalarModeToUseCellData();

        polyDataMapper.SetInput(clipZminusPolyData.GetOutput());

        return polyDataMapper;
	}

	private vtkActor createOutline(vtkImageData vtkImageData) {
		vtkOutlineFilter outlineData = new vtkOutlineFilter();
		outlineData.SetInput(vtkImageData);
		vtkPolyDataMapper mapOutline = new vtkPolyDataMapper();
		mapOutline.SetInput(outlineData.GetOutput());
		vtkActor outlineActor = new vtkActor();
		outlineActor.SetMapper(mapOutline);
		outlineActor.GetProperty().SetColor(1, .49, .25);

		return outlineActor;
	}

	@Override
	public void setColor(Color color) {
		actor.GetProperty().SetColor((float) color.getRed() / 255,
                (float) color.getGreen() / 255,
                (float) color.getBlue() / 255);
		actor.GetProperty().SetEdgeColor((float) color.getRed() / 255,
                (float) color.getGreen() / 255,
                (float) color.getBlue() / 255);
	}

	@Override
	public void setOutline(boolean outline) {
	  if(outlineActor.GetProperty().GetOpacity() != 0) {
		outlineActor.GetProperty().SetOpacity(0);
	  } else {
		  outlineActor.GetProperty().SetOpacity(1);
	  }
	}

	@Override
	public void setOpacity(float f) {
		if (f > 9){
			actorNormals.FlipNormalsOff();
		} else {
        	actorNormals.FlipNormalsOn();
		}

		actor.GetProperty().SetOpacity( f / 10);
	}

	@Override
	public void setDiffuce(float f) {
		imageProperty.setDiffuseColor(f);
		actor.GetProperty().SetDiffuse(f/FACTOR_10);
	}

	@Override
	public void setAmbient(float f) {
		imageProperty.setAmbientColor(f);
		actor.GetProperty().SetAmbient(f/FACTOR_10);
	}

	@Override
	public void setSpecular(float f) {
		imageProperty.setSpecularColor(f);
		actor.GetProperty().SetSpecular(f/FACTOR_10);
	}

	@Override
	public void setIterations(int i) {

	}

	public void setScaleX(int x) {
		if (x <= 0) {
            x = 1;
		}
        actor.SetScale((float) x / FACTOR_10);
        outlineActor.SetScale((float) x / FACTOR_10);
	}

	@Override
	public void setPlaneXy() {

	}

	@Override
	public void setPlaneYz() {

	}

	@Override
	public void setPlaneXz() {

	}

	public vtkActor getOutlineActor() {
		return outlineActor;
	}

	public vtkActor getActor() {
		return this.actor;
	}

	@Override
	public void setRepresentation(RepresentationEnum representationEnum) {
		if(representationEnum == RepresentationEnum.POINT) {
			actor.GetProperty().SetRepresentationToPoints();
		} else if (representationEnum == RepresentationEnum.SOLID) {
			actor.GetProperty().SetRepresentationToSurface();
		} else {
			actor.GetProperty().SetRepresentationToWireframe();
		}

		imageProperty.setRepresentation(representationEnum);
	}

	@Override
	public RepresentationEnum getRepresentation() {
		return imageProperty.getRepresentation();
	}

	@Override
	public void setComponentView(ComponentViewEnum componentViewEnum) {
		if(componentViewEnum == ComponentViewEnum.INNER) {
			actor.GetProperty().BackfaceCullingOn();
			actorNormals.FlipNormalsOn();
		} else {
			actor.GetProperty().BackfaceCullingOff();
			actorNormals.FlipNormalsOn();
		}
	}

	@Override
	public ComponentViewEnum getComponentView() {
		return imageProperty.getComponentsView();
	}

	@Override
	public Color getColor() {
		return imageProperty.getObjectColor();
	}

	@Override
	public void setOutlineActor(vtkActor actor) {
		this.outlineActor = actor;
	}

	@Override
	public float getOpacity() {
		return imageProperty.getOpacity();
	}

	@Override
	public float getDiffuce() {
		return imageProperty.getDiffuseColor();
	}

	@Override
	public float getAmbient() {
		return imageProperty.getAmbientColor();
	}

	@Override
	public float getSpecular() {
		return imageProperty.getSpecularColor();
	}

	@Override
	public void setTriangleQuality(int quality) {
		imageProperty.setTriangleQuality(quality);
		final int MAX_LIMIT = 99;
		if (quality > MAX_LIMIT) {
			quality = MAX_LIMIT;
		}
		actorDecimate.SetTargetReduction(1 - (float) quality / FACTOR_100);
	}

	@Override
	public int getTriangleQuality() {
		return imageProperty.getTriangleQuality();
	}

	@Override
	public int getIteration() {
		return imageProperty.getNumberOfIterations();
	}

	@Override
	public void setScaleX(float x) {
		actor.SetScale(x, 1, 1);
        outlineActor.SetScale(x,1,1);
	}

	public void setScale(float x, float y, float z) {
		actor.SetScale(x, y, z);
        outlineActor.SetScale(x,y,z);
	}

	@Override
	public float getScaleX() {
		return imageProperty.getScaleX();
	}

	@Override
	public void setScaleY(float y) {

	}

	@Override
	public float getScaleY() {
		return imageProperty.getScaleY();
	}

	@Override
	public void setScaleZ(float z) {

	}

	@Override
	public float getScaleZ() {
		return imageProperty.getScaleZ();
	}

	@Override
	public void setClipXminus(int x) {
		imageProperty.setClipXminus(x);
		planeXminus.SetOrigin(x, 0, 0);
	}

	@Override
	public void setClipXplus(int x) {
		imageProperty.setClipXplus(x);
		planeXplus.SetOrigin(x, 0, 0);
	}

	@Override
	public void setClipYplus(int x) {
		imageProperty.setClipYplus(x);
		planeYplus.SetOrigin(0, x, 0);
	}

	@Override
	public void setClipYminus(int x) {
		imageProperty.setClipYminus(x);
		planeYminus.SetOrigin(0, x, 0);
	}

	@Override
	public void setClipZplus(int x) {
		imageProperty.setClipZplus(x);
		planeZplus.SetOrigin(0, 0, x);
	}

	@Override
	public void setClipZminus(int x) {
		imageProperty.setClipZminus(x);
		planeZminus.SetOrigin(0, 0, x);
	}

	@Override
	public int getClipXplus() {
		return imageProperty.getClipXplus();
	}

	@Override
	public int getClipXminus() {
		return imageProperty.getClipXminus();
	}

	@Override
	public int getClipYplus() {
		return imageProperty.getClipYplus();
	}

	@Override
	public int getClipYminus() {
		return imageProperty.getClipYminus();
	}

	@Override
	public int getClipZplus() {
		return imageProperty.getClipZplus();
	}

	@Override
	public int getClipZminus() {
		return imageProperty.getClipZminus();
	}

	@Override
	public void setRelazationFactor(float x) {
		imageProperty.setRelaxationFactor(x);
		actorSmooth.SetRelaxationFactor(x / FACTOR_100);
	}

	@Override
	public float getRelazationFactor() {
		return imageProperty.getRelaxationFactor();
	}

	public ImageProperty getImageProperty() {
		return imageProperty;
	}

	public void setImageProperty(ImageProperty imageProperty) {
		this.imageProperty = imageProperty;
	}

	public void setDicomProperties(Float sliceGap, Float sliceTickness,
			Float xPixelSpacing, Float yPixelSpacing) {

		Float xFactor = 1/xPixelSpacing;
		Float yFactor = 1/yPixelSpacing;
		Float zFactor = (xFactor + yFactor)/2;
		setScale(xFactor, yFactor, zFactor);
	}

}
